--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / database / messages.py
Displaying Raw • Download
meshchatx/src/backend/database/messages.py a9cc4b996b8da8ff414e305825f372ca12dc138d (a9cc4b99) Text, 45.18 KB
T8b949e# SPDX-License-Identifier: 0BSD
Tff7b72import T7ee787json
Tff7b72from T7ee787datetime Tff7b72import Te6edf3UTCTb4b4b4, Te6edf3datetime
Tff7b72from T7ee787.T7ee787provider Tff7b72import Te6edf3DatabaseProvider
Te6edf3_LXMF_UPSERT_FIELDS Tff7b72= Tb4b4b4(
Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffstateTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffprogressTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffmethodTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdelivery_attemptsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnext_delivery_attempt_atTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffields_metaTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_imageTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_audioTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_filesTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_reactionTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_telemetryTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrssiTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffsnrTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffqualityTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffis_spamTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffreply_to_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffattachments_strippedTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_hops_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_interface_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_finding_measureTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_row_hash_hexTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3_LXMF_OPTIONAL_UPSERT_FIELDS Tff7b72= Tffa657frozensetTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffattachments_strippedTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffields_metaTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_imageTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_audioTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_filesTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_reactionTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_telemetryTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3_LXMF_EXPORT_ONLY_KEYS Tff7b72= Tffa657frozensetTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffidTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fflxmf_iconTa5d6ff"Tb4b4b4}Tb4b4b4)
Te6edf3_LXMF_ATTACHMENT_FLAG_KEYS Tff7b72= Tb4b4b4(
Ta5d6ff"Ta5d6ffhas_imageTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_audioTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_filesTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_reactionTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhas_telemetryTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72class T56d364MessageDAOTb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3providerTb4b4b4: Te6edf3DatabaseProviderTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffprovider Tff7b72= Te6edf3provider
Tff7b72selfTff7b72.Td2a8ff_lxmf_columns_cache Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ff_lxmf_upsert_fields_cache Tff7b72= Tff7b72None
Tff7b72def Td2a8ff_lxmf_table_columnsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_lxmf_columns_cache Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA table_info(lxmf_messages)Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_lxmf_columns_cache Tff7b72= Tb4b4b4{Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4}
Tff7b72return Tff7b72selfTff7b72.Td2a8ff_lxmf_columns_cache
Tff7b72def Td2a8ff_lxmf_upsert_field_namesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_lxmf_upsert_fields_cache Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3columns Tff7b72= Tff7b72selfTff7b72.Td2a8ff_lxmf_table_columnsTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_lxmf_upsert_fields_cache Tff7b72= Tb4b4b4[
Te6edf3field
Tff7b72for Te6edf3field Tff7b72in Te6edf3_LXMF_UPSERT_FIELDS
Tff7b72if Te6edf3field Tff7b72in Te6edf3columns Tff7b72or Te6edf3field Tff7b72not Tff7b72in Te6edf3_LXMF_OPTIONAL_UPSERT_FIELDS
Tb4b4b4]
Tff7b72return Tff7b72selfTff7b72.Td2a8ff_lxmf_upsert_fields_cache
Tf0883e@staticmethod
Tff7b72def Td2a8ffnormalize_lxmf_message_for_importTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Tffa657dictTb4b4b4(Te6edf3dataTb4b4b4)
Te6edf3row Tff7b72= Tb4b4b4{
Te6edf3keyTb4b4b4: Te6edf3value
Tff7b72for Te6edf3keyTb4b4b4, Te6edf3value Tff7b72in Te6edf3dataTff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3key Tff7b72not Tff7b72in Te6edf3_LXMF_EXPORT_ONLY_KEYS
Tb4b4b4}
Te6edf3message_hash Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3message_hash Tff7b72or Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3message_hashTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3source_hash Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4)
Te6edf3destination_hash Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3source_hash Tff7b72or Tff7b72not Te6edf3destination_hashTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72if Tff7b72not Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3is_incoming Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4)
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4] Tff7b72= Tb4b4b4(
Te6edf3source_hash Tff7b72if Te6edf3is_incoming Tff7b72in Tb4b4b4(T79c0ff1Tb4b4b4, Tff7b72TrueTb4b4b4, Ta5d6ff"Ta5d6ff1Ta5d6ff"Tb4b4b4) Tff7b72else Te6edf3destination_hash
Tb4b4b4)
Te6edf3fields_value Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3fields_valueTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3fields_valueTb4b4b4)
Tff7b72elif Tffa657isinstanceTb4b4b4(Te6edf3fields_valueTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3stripped Tff7b72= Te6edf3fields_valueTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3strippedTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff{Ta5d6ff"Tb4b4b4) Tff7b72or Te6edf3strippedTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff[Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3parsed Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadsTb4b4b4(Te6edf3strippedTb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3parsedTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3parsedTb4b4b4)
Tff7b72except Te6edf3jsonTff7b72.Td2a8ffJSONDecodeErrorTb4b4b4:
Tff7b72pass
Tff7b72for Te6edf3key Tff7b72in Tb4b4b4(
Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffis_spamTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffattachments_strippedTa5d6ff"Tb4b4b4,
Tff7b72*Te6edf3_LXMF_ATTACHMENT_FLAG_KEYSTb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3key Tff7b72in Te6edf3row Tff7b72and Tffa657isinstanceTb4b4b4(Te6edf3rowTb4b4b4[Te6edf3keyTb4b4b4]Tb4b4b4, Tffa657boolTb4b4b4)Tb4b4b4:
Te6edf3rowTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Tffa657intTb4b4b4(Te6edf3rowTb4b4b4[Te6edf3keyTb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffprogressTa5d6ff"Tb4b4b4) Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffprogressTa5d6ff"Tb4b4b4] Tff7b72= T79c0ff0.0
Tff7b72return Te6edf3row
Tf0883e@staticmethod
Tff7b72def Td2a8ff_enrich_lxmf_message_list_cacheTb4b4b4(Te6edf3dataTb4b4b4: Tffa657dictTb4b4b4) Tff7b72-Tff7b72> Tffa657dictTb4b4b4:
T8b949e"""Fill fields_meta and has_* so list/thread APIs avoid reading fields blobs."""
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787lxmf_utils Tff7b72import Tb4b4b4(
Te6edf3lxmf_fields_attachment_flagsTb4b4b4,
Te6edf3lxmf_fields_without_attachment_bytesTb4b4b4,
Tb4b4b4)
Te6edf3fields_value Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)
Te6edf3fields_dict Tff7b72= Tff7b72None
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3fields_valueTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3fields_dict Tff7b72= Te6edf3fields_value
Tff7b72elif Tffa657isinstanceTb4b4b4(Te6edf3fields_valueTb4b4b4, Tffa657strTb4b4b4) Tff7b72and Te6edf3fields_valueTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3parsed Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadsTb4b4b4(Te6edf3fields_valueTb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3parsedTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3fields_dict Tff7b72= Te6edf3parsed
Tff7b72except Te6edf3jsonTff7b72.Td2a8ffJSONDecodeErrorTb4b4b4:
Te6edf3fields_dict Tff7b72= Tff7b72None
Tff7b72if Te6edf3fields_dict Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3fields_dict Tff7b72= Tb4b4b4{Tb4b4b4}
Te6edf3flags Tff7b72= Te6edf3lxmf_fields_attachment_flagsTb4b4b4(Te6edf3fields_dictTb4b4b4)
Tff7b72for Te6edf3keyTb4b4b4, Te6edf3value Tff7b72in Te6edf3flagsTff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Te6edf3keyTb4b4b4) Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3dataTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Te6edf3value
Tff7b72if Tff7b72not Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffields_metaTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6fffields_metaTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(
Te6edf3lxmf_fields_without_attachment_bytesTb4b4b4(Te6edf3fields_dictTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3data
Tff7b72def Td2a8ffimport_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3messagesTb4b4b4)Tb4b4b4:
Te6edf3imported Tff7b72= T79c0ff0
Te6edf3skipped Tff7b72= T79c0ff0
Te6edf3errors Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3messagesTb4b4b4, Tffa657listTb4b4b4)Tb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffmessages must be an arrayTa5d6ff"Tb4b4b4)
Tff7b72for Te6edf3indexTb4b4b4, Te6edf3message Tff7b72in Tffa657enumerateTb4b4b4(Te6edf3messagesTb4b4b4)Tb4b4b4:
Te6edf3normalized Tff7b72= Tff7b72selfTff7b72.Td2a8ffnormalize_lxmf_message_for_importTb4b4b4(Te6edf3messageTb4b4b4)
Tff7b72if Te6edf3normalized Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3skipped Tff7b72+Tff7b72= T79c0ff1
Tff7b72continue
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffupsert_lxmf_messageTb4b4b4(Te6edf3normalizedTb4b4b4)
Te6edf3imported Tff7b72+Tff7b72= T79c0ff1
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Te6edf3errorsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffindexTa5d6ff"Tb4b4b4: Te6edf3indexTb4b4b4,
Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4: Te6edf3normalizedTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6fferrorTa5d6ff"Tb4b4b4: Tffa657strTb4b4b4(Te6edf3excTb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffimportedTa5d6ff"Tb4b4b4: Te6edf3importedTb4b4b4,
Ta5d6ff"Ta5d6ffskippedTa5d6ff"Tb4b4b4: Te6edf3skippedTb4b4b4,
Ta5d6ff"Ta5d6fferrorsTa5d6ff"Tb4b4b4: Te6edf3errorsTb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffupsert_lxmf_messageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e# Ensure data is a dict if it's a sqlite3.Row
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Tffa657dictTb4b4b4(Te6edf3dataTb4b4b4)
Te6edf3data Tff7b72= Tff7b72selfTff7b72.Td2a8ff_enrich_lxmf_message_list_cacheTb4b4b4(Te6edf3dataTb4b4b4)
Te6edf3fields Tff7b72= Tff7b72selfTff7b72.Td2a8ff_lxmf_upsert_field_namesTb4b4b4(Tb4b4b4)
Te6edf3columns Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3fieldsTb4b4b4)
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3fieldsTb4b4b4)Tb4b4b4)
Te6edf3update_fields Tff7b72= Tb4b4b4[
Te6edf3f
Tff7b72for Te6edf3f Tff7b72in Te6edf3fields
Tff7b72if Te6edf3f Tff7b72!= Ta5d6ff"Ta5d6ffhashTa5d6ff"
Tff7b72and Te6edf3f
Tff7b72not Tff7b72in Tb4b4b4(
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_hops_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_interface_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_finding_measureTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_row_hash_hexTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4]
Te6edf3update_set Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6fffTa5d6ff"Tffd700{Te6edf3fTffd700}Ta5d6ff = EXCLUDED.Tffd700{Te6edf3fTffd700}Ta5d6ff" Tff7b72for Te6edf3f Tff7b72in Te6edf3update_fieldsTb4b4b4]Tb4b4b4)
Te6edf3query Tff7b72= Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffINSERT INTO lxmf_messages (Tffd700{Te6edf3columnsTffd700}Ta5d6ff, created_at, updated_at) VALUES (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff, ?, ?) Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffON CONFLICT(hash) DO UPDATE SET Tffd700{Te6edf3update_setTffd700}Ta5d6ff, updated_at = EXCLUDED.updated_atTa5d6ff"
Tb4b4b4)
Te6edf3params Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3field Tff7b72in Te6edf3fieldsTb4b4b4:
Te6edf3val Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Te6edf3fieldTb4b4b4)
Tff7b72if Te6edf3field Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fffields_metaTa5d6ff"Tb4b4b4) Tff7b72and Tffa657isinstanceTb4b4b4(Te6edf3valTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3val Tff7b72= Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3valTb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3valTb4b4b4)
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Te6edf3created_at Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4)
Te6edf3updated_at Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffupdated_atTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3created_atTb4b4b4, Tffa657strTb4b4b4) Tff7b72or Tff7b72not Te6edf3created_atTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3created_at Tff7b72= Te6edf3now
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3updated_atTb4b4b4, Tffa657strTb4b4b4) Tff7b72or Tff7b72not Te6edf3updated_atTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3updated_at Tff7b72= Te6edf3now
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3created_atTb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3updated_atTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
Te6edf3peer_hash Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3peer_hashTb4b4b4, Tffa657strTb4b4b4) Tff7b72and Te6edf3peer_hashTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffrefresh_conversation_summaryTb4b4b4(Te6edf3peer_hashTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72def Td2a8ffrefresh_conversation_summaryTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
T8b949e"""Rebuild the materialized list row for one peer.
Conversation list queries read lxmf_conversation_summaries so they do
not GROUP BY the full messages table on every refresh.
"""
Tff7b72if Tff7b72not Te6edf3peer_hash Tff7b72or Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3peer_hashTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return
Te6edf3peer_hash Tff7b72= Te6edf3peer_hashTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3peer_hashTb4b4b4:
Tff7b72return
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
T8b949e"""
SELECT
id, hash, source_hash, destination_hash, peer_hash, state, progress,
is_incoming, title,
substr(COALESCE(content, ''), 1, 240) AS content_preview,
timestamp, is_spam, reply_to_hash, created_at, updated_at,
COALESCE(has_image, 0) AS has_image,
COALESCE(has_audio, 0) AS has_audio,
COALESCE(has_files, 0) AS has_files,
COALESCE(has_reaction, 0) AS has_reaction,
COALESCE(has_telemetry, 0) AS has_telemetry
FROM lxmf_messages
WHERE peer_hash = ?
ORDER BY id DESC
LIMIT 1
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3rowTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_summaries WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return
Te6edf3failed_row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
T8b949e"""
SELECT COUNT(*) AS failed_count
FROM lxmf_messages
WHERE peer_hash = ? AND state = 'failed'
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3failed_count Tff7b72= Tffa657intTb4b4b4(Te6edf3failed_rowTb4b4b4[Ta5d6ff"Ta5d6fffailed_countTa5d6ff"Tb4b4b4] Tff7b72or T79c0ff0Tb4b4b4) Tff7b72if Te6edf3failed_row Tff7b72else T79c0ff0
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_summaries (
peer_hash, latest_message_id, latest_message_hash,
source_hash, destination_hash, state, progress, is_incoming,
title, content_preview, timestamp, is_spam, reply_to_hash,
created_at, updated_at,
has_image, has_audio, has_files, has_reaction, has_telemetry,
failed_count
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(peer_hash) DO UPDATE SET
latest_message_id = EXCLUDED.latest_message_id,
latest_message_hash = EXCLUDED.latest_message_hash,
source_hash = EXCLUDED.source_hash,
destination_hash = EXCLUDED.destination_hash,
state = EXCLUDED.state,
progress = EXCLUDED.progress,
is_incoming = EXCLUDED.is_incoming,
title = EXCLUDED.title,
content_preview = EXCLUDED.content_preview,
timestamp = EXCLUDED.timestamp,
is_spam = EXCLUDED.is_spam,
reply_to_hash = EXCLUDED.reply_to_hash,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at,
has_image = EXCLUDED.has_image,
has_audio = EXCLUDED.has_audio,
has_files = EXCLUDED.has_files,
has_reaction = EXCLUDED.has_reaction,
has_telemetry = EXCLUDED.has_telemetry,
failed_count = EXCLUDED.failed_count
"""Tb4b4b4,
Tb4b4b4(
Te6edf3peer_hashTb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffidTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffstateTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffprogressTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcontent_previewTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffis_spamTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffreply_to_hashTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffupdated_atTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhas_imageTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhas_audioTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhas_filesTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhas_reactionTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhas_telemetryTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3failed_countTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffrefresh_conversation_summaries_for_peersTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashesTb4b4b4)Tb4b4b4:
Te6edf3seen Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72for Te6edf3peer_hash Tff7b72in Te6edf3peer_hashes Tff7b72or Tb4b4b4[Tb4b4b4]Tb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3peer_hashTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72continue
Te6edf3key Tff7b72= Te6edf3peer_hashTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3key Tff7b72or Te6edf3key Tff7b72in Te6edf3seenTb4b4b4:
Tff7b72continue
Te6edf3seenTff7b72.Td2a8ffaddTb4b4b4(Te6edf3keyTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffrefresh_conversation_summaryTb4b4b4(Te6edf3keyTb4b4b4)
Tff7b72def Td2a8ffdelete_conversation_summaryTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3peer_hash Tff7b72or Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3peer_hashTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_summaries WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffset_lxmf_message_path_at_send_if_unsetTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3message_hashTb4b4b4,
Te6edf3hopsTb4b4b4,
Te6edf3interface_nameTb4b4b4,
Tb4b4b4)Tb4b4b4:
T8b949e"""Store Reticulum path snapshot once (send or receive); never overwrites."""
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET path_hops_at_send = ?, path_interface_at_send = ?, updated_at = ? Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE hash = ? AND path_hops_at_send IS NULLTa5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3hopsTb4b4b4, Te6edf3interface_nameTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffupdate_lxmf_message_stateTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3message_hashTb4b4b4,
Te6edf3stateTb4b4b4,
Te6edf3progressTb4b4b4,
Te6edf3delivery_attemptsTb4b4b4,
Te6edf3next_delivery_attempt_atTb4b4b4,
Te6edf3rssiTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3snrTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3qualityTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3methodTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
T8b949e"""Lightweight update for delivery-state changes only.
Avoids re-serializing the full message (including base64 attachment
data) which the heavy upsert_lxmf_message path does.
"""
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3method Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET state = ?, progress = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffdelivery_attempts = ?, next_delivery_attempt_at = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffrssi = ?, snr = ?, quality = ?, updated_at = ? Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(
Te6edf3stateTb4b4b4,
Te6edf3progressTb4b4b4,
Te6edf3delivery_attemptsTb4b4b4,
Te6edf3next_delivery_attempt_atTb4b4b4,
Te6edf3rssiTb4b4b4,
Te6edf3snrTb4b4b4,
Te6edf3qualityTb4b4b4,
Te6edf3nowTb4b4b4,
Te6edf3message_hashTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET state = ?, progress = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffdelivery_attempts = ?, next_delivery_attempt_at = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffrssi = ?, snr = ?, quality = ?, method = ?, updated_at = ? Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(
Te6edf3stateTb4b4b4,
Te6edf3progressTb4b4b4,
Te6edf3delivery_attemptsTb4b4b4,
Te6edf3next_delivery_attempt_atTb4b4b4,
Te6edf3rssiTb4b4b4,
Te6edf3snrTb4b4b4,
Te6edf3qualityTb4b4b4,
Te6edf3methodTb4b4b4,
Te6edf3nowTb4b4b4,
Te6edf3message_hashTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT peer_hash FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffrefresh_conversation_summaryTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72def Td2a8ffget_lxmf_message_by_hashTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8fflist_message_hashes_for_peerTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT hash FROM lxmf_messages WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4[Te6edf3rTb4b4b4[Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3r Tff7b72in Te6edf3rowsTb4b4b4]
Tff7b72def Td2a8ffget_pinned_peer_hashesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT peer_hash FROM lxmf_conversation_pins ORDER BY pinned_at DESCTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4[Te6edf3rTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3r Tff7b72in Te6edf3rowsTb4b4b4]
Tff7b72def Td2a8ffis_peer_pinnedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT 1 AS ok FROM lxmf_conversation_pins WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3row Tff7b72is Tff7b72not Tff7b72None
Tff7b72def Td2a8ffset_peer_pinnedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4, Te6edf3pinnedTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3pinnedTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_pins (peer_hash, pinned_at)
VALUES (?, strftime('%s', 'now'))
ON CONFLICT(peer_hash) DO UPDATE SET pinned_at = EXCLUDED.pinned_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_pins WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8fftoggle_peer_pinTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffis_peer_pinnedTb4b4b4(Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffset_peer_pinnedTb4b4b4(Te6edf3peer_hashTb4b4b4, Tff7b72FalseTb4b4b4)
Tff7b72return Tff7b72False
Tff7b72selfTff7b72.Td2a8ffset_peer_pinnedTb4b4b4(Te6edf3peer_hashTb4b4b4, Tff7b72TrueTb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8fflist_message_hashes_with_timestamp_beforeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3cutoff_tsTb4b4b4: Tffa657floatTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT hash FROM lxmf_messages WHERE timestamp IS NOT NULL AND timestamp < ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3cutoff_tsTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4[Te6edf3rTb4b4b4[Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3r Tff7b72in Te6edf3rows Tff7b72if Te6edf3rTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4)Tb4b4b4]
Tff7b72def Td2a8ffcount_lxmf_messages_with_timestamp_beforeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3cutoff_tsTb4b4b4: Tffa657floatTb4b4b4) Tff7b72-Tff7b72> Tffa657intTb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT COUNT(*) AS count FROM lxmf_messages Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE timestamp IS NOT NULL AND timestamp < ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3cutoff_tsTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tffa657intTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4]Tb4b4b4) Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else T79c0ff0
Tff7b72def Td2a8ffget_lxmf_messages_with_timestamp_beforeTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3cutoff_tsTb4b4b4: Tffa657floatTb4b4b4,
Te6edf3limitTb4b4b4: Tffa657int Tff7b72= T79c0ff5000Tb4b4b4,
Te6edf3offsetTb4b4b4: Tffa657int Tff7b72= T79c0ff0Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE timestamp IS NOT NULL AND timestamp < ? Ta5d6ff"
Ta5d6ff"Ta5d6ffORDER BY id LIMIT ? OFFSET ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3cutoff_tsTb4b4b4, Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffprune_conversation_metadata_for_peers_with_no_messagesTb4b4b4(Tff7b72selfTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
DELETE FROM lxmf_conversation_read_state
WHERE destination_hash NOT IN (
SELECT DISTINCT peer_hash FROM lxmf_messages WHERE peer_hash IS NOT NULL
)
"""Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
DELETE FROM lxmf_conversation_folders
WHERE peer_hash NOT IN (
SELECT DISTINCT peer_hash FROM lxmf_messages WHERE peer_hash IS NOT NULL
)
"""Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
DELETE FROM lxmf_conversation_pins
WHERE peer_hash NOT IN (
SELECT DISTINCT peer_hash FROM lxmf_messages WHERE peer_hash IS NOT NULL
)
"""Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffdelete_lxmf_messages_by_hashesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3message_hashesTb4b4b4:
Tff7b72return
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3message_hashesTb4b4b4)Tb4b4b4)
Te6edf3peers Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffSELECT DISTINCT peer_hash FROM lxmf_messages WHERE hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3message_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDELETE FROM lxmf_messages WHERE hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3message_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffrefresh_conversation_summaries_for_peersTb4b4b4(
Tb4b4b4[Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3peers Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4)Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffdelete_lxmf_message_by_hashTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT peer_hash FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffrefresh_conversation_summaryTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72def Td2a8ffdelete_all_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_messagesTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_read_stateTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_summariesTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffget_all_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3limitTff7b72=T79c0ff5000Tb4b4b4, Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages ORDER BY id LIMIT ? OFFSET ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffcount_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(Ta5d6ff"Ta5d6ffSELECT COUNT(*) AS count FROM lxmf_messagesTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else T79c0ff0
Tff7b72def Td2a8ffcount_lxmf_messages_by_stateTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3stateTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT COUNT(*) AS count FROM lxmf_messages WHERE state = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3stateTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else T79c0ff0
Tff7b72def Td2a8ffget_conversation_messagesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3limitTff7b72=T79c0ff100Tb4b4b4, Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages WHERE peer_hash = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffget_latest_user_facing_incoming_messageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4, Tff7b72*Tb4b4b4, Te6edf3scan_limitTff7b72=T79c0ff50Tb4b4b4)Tb4b4b4:
T8b949e"""Return the most recent incoming user-facing message for peer_hash.
Walks recent incoming messages in timestamp-descending order and applies
is_user_facing_lxmf_payload in Python (the SQLite layer cannot
cheaply parse the JSON fields blob). scan_limit bounds the walk
so a long chain of reactions/telemetry won't degrade the bell endpoint.
Returns None if no user-facing incoming message exists in the
scanned window.
"""
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787lxmf_utils Tff7b72import Te6edf3is_user_facing_lxmf_payload
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT id, hash, peer_hash, source_hash, destination_hash, Ta5d6ff"
Ta5d6ff"Ta5d6ffis_incoming, title, Ta5d6ff"
Ta5d6ff"Ta5d6ffsubstr(COALESCE(content, Ta5d6ff'Ta5d6ff'Ta5d6ff), 1, 240) as content, Ta5d6ff"
Ta5d6ff"Ta5d6ffCASE WHEN length(COALESCE(fields, Ta5d6ff'Ta5d6ff'Ta5d6ff)) > 16384 THEN NULL ELSE fields END as fields, Ta5d6ff"
Ta5d6ff"Ta5d6ffCASE WHEN fields IS NOT NULL AND fields != Ta5d6ff'Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Tffd700{}Ta5d6ff'Ta5d6ff Ta5d6ff"
Ta5d6ff"Ta5d6ffAND (instr(fields, Ta5d6ff'Tffea00\"Ta5d6ffimageTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x05Tffea00\"Ta5d6ff'Ta5d6ff) > 0 Ta5d6ff"
Ta5d6ff"Ta5d6ffOR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ffaudioTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x06Tffea00\"Ta5d6ff'Ta5d6ff) > 0 Ta5d6ff"
Ta5d6ff"Ta5d6ffOR instr(fields, Ta5d6ff'Tffea00\"Ta5d6fffile_attachmentsTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x07Tffea00\"Ta5d6ff'Ta5d6ff) > 0) Ta5d6ff"
Ta5d6ff"Ta5d6ffTHEN 1 ELSE 0 END as has_attachments, Ta5d6ff"
Ta5d6ff"Ta5d6ffCASE WHEN fields IS NOT NULL AND fields != Ta5d6ff'Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Tffd700{}Ta5d6ff'Ta5d6ff Ta5d6ff"
Ta5d6ff"Ta5d6ffAND (instr(fields, Ta5d6ff'Tffea00\"Ta5d6ffreactionTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x40Tffea00\"Ta5d6ff'Ta5d6ff) > 0) Ta5d6ff"
Ta5d6ff"Ta5d6ffTHEN 1 ELSE 0 END as has_reaction, Ta5d6ff"
Ta5d6ff"Ta5d6ffCASE WHEN fields IS NOT NULL AND fields != Ta5d6ff'Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Tffd700{}Ta5d6ff'Ta5d6ff Ta5d6ff"
Ta5d6ff"Ta5d6ffAND (instr(fields, Ta5d6ff'Tffea00\"Ta5d6ffimageTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x05Tffea00\"Ta5d6ff'Ta5d6ff) > 0) Ta5d6ff"
Ta5d6ff"Ta5d6ffTHEN 1 ELSE 0 END as has_image, Ta5d6ff"
Ta5d6ff"Ta5d6ffCASE WHEN fields IS NOT NULL AND fields != Ta5d6ff'Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Tffd700{}Ta5d6ff'Ta5d6ff Ta5d6ff"
Ta5d6ff"Ta5d6ffAND (instr(fields, Ta5d6ff'Tffea00\"Ta5d6ffaudioTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x06Tffea00\"Ta5d6ff'Ta5d6ff) > 0) Ta5d6ff"
Ta5d6ff"Ta5d6ffTHEN 1 ELSE 0 END as has_audio, Ta5d6ff"
Ta5d6ff"Ta5d6ffCASE WHEN fields IS NOT NULL AND fields != Ta5d6ff'Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Tffd700{}Ta5d6ff'Ta5d6ff Ta5d6ff"
Ta5d6ff"Ta5d6ffAND (instr(fields, Ta5d6ff'Tffea00\"Ta5d6fffile_attachmentsTffea00\"Ta5d6ff'Ta5d6ff) > 0 OR instr(fields, Ta5d6ff'Tffea00\"Ta5d6ff0x07Tffea00\"Ta5d6ff'Ta5d6ff) > 0) Ta5d6ff"
Ta5d6ff"Ta5d6ffTHEN 1 ELSE 0 END as has_files, Ta5d6ff"
Ta5d6ff"Ta5d6fftimestamp Ta5d6ff"
Ta5d6ff"Ta5d6ffFROM lxmf_messages WHERE peer_hash = ? AND is_incoming = 1 Ta5d6ff"
Ta5d6ff"Ta5d6ffORDER BY timestamp DESC LIMIT ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4, Te6edf3scan_limitTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Te6edf3row_dict Tff7b72= Tffa657dictTb4b4b4(Te6edf3rowTb4b4b4) Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3rowTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Te6edf3row
Te6edf3fields Tff7b72= Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3fields Tff7b72is Tff7b72None Tff7b72and Tb4b4b4(
Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhas_attachmentsTa5d6ff"Tb4b4b4)
Tff7b72or Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhas_imageTa5d6ff"Tb4b4b4)
Tff7b72or Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhas_audioTa5d6ff"Tb4b4b4)
Tff7b72or Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhas_filesTa5d6ff"Tb4b4b4)
Tb4b4b4)Tb4b4b4:
T8b949e# Huge attachment blob omitted from SELECT: still user-facing.
Tff7b72return Te6edf3row_dict
Tff7b72if Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhas_reactionTa5d6ff"Tb4b4b4) Tff7b72and Tff7b72not Tb4b4b4(
Tb4b4b4(Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4) Tff7b72and Tffa657strTb4b4b4(Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72or Tb4b4b4(Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4) Tff7b72and Tffa657strTb4b4b4(Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72or Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhas_attachmentsTa5d6ff"Tb4b4b4)
Tb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72if Te6edf3is_user_facing_lxmf_payloadTb4b4b4(
Te6edf3fieldsTb4b4b4,
Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3row_dict
Tff7b72return Tff7b72None
Te6edf3CONVERSATION_LIST_COLUMNS Tff7b72= Tb4b4b4(
Ta5d6ff"Ta5d6ffm1.id, m1.hash, m1.source_hash, m1.destination_hash, m1.peer_hash, Ta5d6ff"
Ta5d6ff"Ta5d6ffm1.state, m1.is_incoming, m1.title, m1.timestamp, m1.created_at, m1.updated_atTa5d6ff"
Tb4b4b4)
Tff7b72def Td2a8ffget_conversationsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT Tffd700{Tff7b72selfTff7b72.Td2a8ffCONVERSATION_LIST_COLUMNSTffd700}
Ta5d6ff FROM lxmf_conversation_summaries s
Ta5d6ff INNER JOIN lxmf_messages m1 ON m1.id = s.latest_message_id
Ta5d6ff ORDER BY s.latest_message_id DESC
Ta5d6ff Ta5d6ff"""
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4)
Tff7b72def Td2a8ffmark_conversation_as_readTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state (destination_hash, last_read_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffmark_conversations_as_readTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecutemanyTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state (destination_hash, last_read_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4[Tb4b4b4(Te6edf3hTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3destination_hashesTb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffmark_all_conversations_as_readTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state (destination_hash, last_read_at, created_at, updated_at)
SELECT peer_hash, ?, ?, ? FROM lxmf_messages
WHERE peer_hash IS NOT NULL
GROUP BY peer_hash
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffget_all_conversation_read_stateTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT destination_hash, last_read_at, created_at, updated_at Ta5d6ff"
Ta5d6ff"Ta5d6ffFROM lxmf_conversation_read_stateTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffimport_conversation_read_stateTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3rowsTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3rowsTb4b4b4, Tffa657listTb4b4b4) Tff7b72or Tff7b72not Te6edf3rowsTb4b4b4:
Tff7b72return T79c0ff0
Te6edf3imported Tff7b72= T79c0ff0
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3rowTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Tff7b72continue
Te6edf3dest Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4)
Te6edf3last_read_at Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3dest Tff7b72or Tff7b72not Te6edf3last_read_atTb4b4b4:
Tff7b72continue
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state
(destination_hash, last_read_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3destTb4b4b4, Te6edf3last_read_atTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3imported Tff7b72+Tff7b72= T79c0ff1
Tff7b72return Te6edf3imported
Tff7b72def Td2a8ffget_all_notification_viewed_stateTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT destination_hash, last_viewed_at, created_at, updated_at Ta5d6ff"
Ta5d6ff"Ta5d6ffFROM notification_viewed_stateTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffimport_notification_viewed_stateTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3rowsTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3rowsTb4b4b4, Tffa657listTb4b4b4) Tff7b72or Tff7b72not Te6edf3rowsTb4b4b4:
Tff7b72return T79c0ff0
Te6edf3imported Tff7b72= T79c0ff0
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3rowTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Tff7b72continue
Te6edf3dest Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4)
Te6edf3last_viewed_at Tff7b72= Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflast_viewed_atTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3dest Tff7b72or Tff7b72not Te6edf3last_viewed_atTb4b4b4:
Tff7b72continue
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state
(destination_hash, last_viewed_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3destTb4b4b4, Te6edf3last_viewed_atTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3imported Tff7b72+Tff7b72= T79c0ff1
Tff7b72return Te6edf3imported
Tff7b72def Td2a8ffis_conversation_unreadTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
T8b949e"""
SELECT m.timestamp, r.last_read_at
FROM lxmf_messages m
LEFT JOIN lxmf_conversation_read_state r ON r.destination_hash = ?
WHERE m.peer_hash = ? AND m.is_incoming = 1
ORDER BY m.timestamp DESC LIMIT 1
"""Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3rowTb4b4b4:
Tff7b72return Tff7b72False
Tff7b72if Tff7b72not Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4]Tb4b4b4:
Tff7b72return Tff7b72True
Te6edf3last_read_at Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3last_read_atTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_read_at Tff7b72= Te6edf3last_read_atTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4] Tff7b72> Te6edf3last_read_atTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffmark_stuck_messages_as_failedTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
UPDATE lxmf_messages
SET state = 'generating'
WHERE is_incoming = 1 AND state = 'failed'
"""Tb4b4b4,
Tb4b4b4)
T8b949e# Only outbound messages can get stuck mid-send, as incoming messages are
T8b949e# never failed (we already received them).
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
UPDATE lxmf_messages
SET state = 'failed', updated_at = ?
WHERE is_incoming = 0
AND (
state = 'outbound'
OR (state = 'sent' AND method = 'opportunistic')
OR state = 'sending'
OR state = 'generating'
)
"""Tb4b4b4,
Tb4b4b4(Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8fflist_duplicate_lxmf_message_hashes_by_contentTb4b4b4(Tff7b72selfTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4:
T8b949e"""Hashes of duplicate rows (same peer, direction, and text), excluding the oldest keep.
Empty or whitespace-only content is ignored so attachment-only or blank
rows are not collapsed together. Oldest is by timestamp then id.
"""
Te6edf3groups Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
T8b949e"""
SELECT peer_hash, is_incoming, content
FROM lxmf_messages
WHERE content IS NOT NULL AND TRIM(content) != ''
GROUP BY peer_hash, is_incoming, content
HAVING COUNT(*) > 1
"""Tb4b4b4,
Tb4b4b4)
Te6edf3to_deleteTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4] Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3group Tff7b72in Te6edf3groupsTb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
T8b949e"""
SELECT hash
FROM lxmf_messages
WHERE peer_hash = ?
AND is_incoming = ?
AND content = ?
ORDER BY
CASE WHEN timestamp IS NULL THEN 1 ELSE 0 END,
timestamp ASC,
id ASC
"""Tb4b4b4,
Tb4b4b4(Te6edf3groupTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3groupTb4b4b4[Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3groupTb4b4b4[Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4,
Tb4b4b4)
T8b949e# Keep the first (oldest); delete the rest.
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4[T79c0ff1Tb4b4b4:Tb4b4b4]Tb4b4b4:
Tff7b72if Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3to_deleteTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72return Te6edf3to_delete
Tff7b72def Td2a8ffcount_duplicate_lxmf_messages_by_contentTb4b4b4(Tff7b72selfTb4b4b4) Tff7b72-Tff7b72> Tffa657intTb4b4b4:
Tff7b72return Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8fflist_duplicate_lxmf_message_hashes_by_contentTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72def Td2a8ffdelete_duplicate_lxmf_messages_by_contentTb4b4b4(Tff7b72selfTb4b4b4) Tff7b72-Tff7b72> Tffa657intTb4b4b4:
T8b949e"""Delete content-duplicate message rows, keeping the oldest per group."""
Te6edf3hashes Tff7b72= Tff7b72selfTff7b72.Td2a8fflist_duplicate_lxmf_message_hashes_by_contentTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3hashesTb4b4b4:
Tff7b72return T79c0ff0
Tff7b72selfTff7b72.Td2a8ffdelete_lxmf_messages_by_hashesTb4b4b4(Te6edf3hashesTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffprune_conversation_metadata_for_peers_with_no_messagesTb4b4b4(Tb4b4b4)
Tff7b72return Tffa657lenTb4b4b4(Te6edf3hashesTb4b4b4)
Tff7b72def Td2a8ffget_failed_messages_for_destinationTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages WHERE state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff AND peer_hash = ? ORDER BY id ASCTa5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8fftry_claim_failed_message_for_auto_resendTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3message_hashTb4b4b4: Tffa657strTb4b4b4,
Tff7b72*Tb4b4b4,
Te6edf3cooldown_untilTb4b4b4: Tffa657floatTb4b4b4,
Te6edf3nowTb4b4b4: Tffa657floatTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
T8b949e"""Atomically claim a failed row for one auto-resend attempt.
Sets next_delivery_attempt_at into the future so overlapping announce,
ping, and path handlers cannot claim the same row again until cooldown.
"""
Te6edf3now_iso Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Te6edf3cursor Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
UPDATE lxmf_messages
SET next_delivery_attempt_at = ?, updated_at = ?
WHERE hash = ?
AND state = 'failed'
AND (
next_delivery_attempt_at IS NULL
OR next_delivery_attempt_at <= ?
)
"""Tb4b4b4,
Tb4b4b4(Tffa657floatTb4b4b4(Te6edf3cooldown_untilTb4b4b4)Tb4b4b4, Te6edf3now_isoTb4b4b4, Te6edf3message_hashTb4b4b4, Tffa657floatTb4b4b4(Te6edf3nowTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tffa657boolTb4b4b4(Te6edf3cursor Tff7b72and Te6edf3cursorTff7b72.Td2a8ffrowcount Tff7b72and Te6edf3cursorTff7b72.Td2a8ffrowcount Tff7b72> T79c0ff0Tb4b4b4)
Tff7b72def Td2a8ffset_message_fields_jsonTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4: Tffa657strTb4b4b4, Te6edf3fields_jsonTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Te6edf3now_iso Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET fields = ?, updated_at = ? WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3fields_jsonTb4b4b4, Te6edf3now_isoTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffset_auto_resend_count_on_messageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4: Tffa657strTb4b4b4, Te6edf3countTb4b4b4: Tffa657intTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787auto_resend_guard Tff7b72import Tb4b4b4(
Te6edf3fields_with_auto_resend_countTb4b4b4,
Tb4b4b4)
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT fields FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3rowTb4b4b4:
Tff7b72return
Tff7b72selfTff7b72.Td2a8ffset_message_fields_jsonTb4b4b4(
Te6edf3message_hashTb4b4b4,
Te6edf3fields_with_auto_resend_countTb4b4b4(Te6edf3rowTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3countTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffhas_recent_outbound_with_contentTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3peer_hashTb4b4b4: Tffa657strTb4b4b4,
Te6edf3contentTb4b4b4: Tffa657str Tff7b72| Tff7b72NoneTb4b4b4,
Tff7b72*Tb4b4b4,
Te6edf3within_secondsTb4b4b4: Tffa657floatTb4b4b4,
Te6edf3nowTb4b4b4: Tffa657float Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
T8b949e"""True when a recent non-failed outbound already carries the same body."""
Tff7b72import T7ee787time Tff7b72as T7ee787_time
Te6edf3now_ts Tff7b72= Tffa657floatTb4b4b4(Te6edf3now Tff7b72if Te6edf3now Tff7b72is Tff7b72not Tff7b72None Tff7b72else Te6edf3_timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3cutoff Tff7b72= Te6edf3now_ts Tff7b72- Tffa657floatTb4b4b4(Te6edf3within_secondsTb4b4b4)
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
T8b949e"""
SELECT 1 AS ok FROM lxmf_messages
WHERE peer_hash = ?
AND is_incoming = 0
AND state != 'failed'
AND content = ?
AND timestamp IS NOT NULL
AND timestamp >= ?
LIMIT 1
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4, Te6edf3content Tff7b72if Te6edf3content Tff7b72is Tff7b72not Tff7b72None Tff7b72else Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3cutoffTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tffa657boolTb4b4b4(Te6edf3rowTb4b4b4)
Tff7b72def Td2a8ffget_failed_messages_countTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT COUNT(*) as count FROM lxmf_messages WHERE state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff AND peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72if Te6edf3row Tff7b72else T79c0ff0
Tff7b72def Td2a8ffget_conversations_unread_statesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4)
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT peer_hash, MAX(timestamp) as latest_ts, last_read_at
Ta5d6ff FROM lxmf_messages m
Ta5d6ff LEFT JOIN lxmf_conversation_read_state r ON r.destination_hash = m.peer_hash
Ta5d6ff WHERE m.peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff) AND m.is_incoming = 1
Ta5d6ff GROUP BY m.peer_hash
Ta5d6ff Ta5d6ff"""
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3destination_hashesTb4b4b4)
Te6edf3unread_states Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Te6edf3peer_hash Tff7b72= Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]
Te6edf3latest_ts Tff7b72= Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflatest_tsTa5d6ff"Tb4b4b4]
Te6edf3last_read_at_str Tff7b72= Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4]
Tff7b72if Tff7b72not Te6edf3last_read_at_strTb4b4b4:
Te6edf3unread_statesTb4b4b4[Te6edf3peer_hashTb4b4b4] Tff7b72= Tff7b72True
Tff7b72continue
Te6edf3last_read_at Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(Te6edf3last_read_at_strTb4b4b4)
Tff7b72if Te6edf3last_read_atTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_read_at Tff7b72= Te6edf3last_read_atTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4)
Te6edf3unread_statesTb4b4b4[Te6edf3peer_hashTb4b4b4] Tff7b72= Te6edf3latest_ts Tff7b72> Te6edf3last_read_atTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3unread_states
Tff7b72def Td2a8ffget_conversations_failed_countsTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4)
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffSELECT peer_hash, COUNT(*) as count FROM lxmf_messages WHERE state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff AND peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff) GROUP BY peer_hashTa5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4{Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4: Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4}
Tff7b72def Td2a8ffget_conversations_attachment_statesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4)
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT peer_hash, 1 as has_attachments
Ta5d6ff FROM lxmf_messages
Ta5d6ff WHERE peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)
Ta5d6ff AND fields IS NOT NULL AND fields != Ta5d6ff'Tffea00{{Tffea00}}Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Ta5d6ff'
Ta5d6ff GROUP BY peer_hash
Ta5d6ff Ta5d6ff"""
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3destination_hashesTb4b4b4)
Tff7b72return Tb4b4b4{Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4: Tff7b72True Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4}
T8b949e# Forwarding Mappings
Tff7b72def Td2a8ffget_forwarding_mappingTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3alias_hashTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3original_sender_hashTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3final_recipient_hashTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3alias_hashTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_forwarding_mappings WHERE alias_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3alias_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3original_sender_hash Tff7b72and Te6edf3final_recipient_hashTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_forwarding_mappings WHERE original_sender_hash = ? AND final_recipient_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3original_sender_hashTb4b4b4, Te6edf3final_recipient_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffcreate_forwarding_mappingTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e# Ensure data is a dict if it's a sqlite3.Row
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Tffa657dictTb4b4b4(Te6edf3dataTb4b4b4)
Te6edf3fields Tff7b72= Tb4b4b4[
Ta5d6ff"Ta5d6ffalias_identity_private_keyTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffalias_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fforiginal_sender_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffinal_recipient_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fforiginal_destination_hashTa5d6ff"Tb4b4b4,
Tb4b4b4]
Te6edf3columns Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3fieldsTb4b4b4)
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3fieldsTb4b4b4)Tb4b4b4)
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffINSERT INTO lxmf_forwarding_mappings (Tffd700{Te6edf3columnsTffd700}Ta5d6ff, created_at) VALUES (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff, ?)Ta5d6ff"
Te6edf3params Tff7b72= Tb4b4b4[Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Te6edf3fTb4b4b4) Tff7b72for Te6edf3f Tff7b72in Te6edf3fieldsTb4b4b4]
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
Tff7b72def Td2a8ffget_all_forwarding_mappingsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_forwarding_mappingsTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffmark_notification_as_viewedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state (destination_hash, last_viewed_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffmark_all_notifications_as_viewedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3destination_hashesTb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecutemanyTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state (destination_hash, last_viewed_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4[Tb4b4b4(Te6edf3hTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3destination_hashesTb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# mark all conversations as viewed
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state (destination_hash, last_viewed_at, created_at, updated_at)
SELECT peer_hash, ?, ?, ? FROM lxmf_messages
WHERE peer_hash IS NOT NULL
GROUP BY peer_hash
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffis_notification_viewedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3message_timestampTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT last_viewed_at FROM notification_viewed_state WHERE destination_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3row Tff7b72or Tff7b72not Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_viewed_atTa5d6ff"Tb4b4b4]Tb4b4b4:
Tff7b72return Tff7b72False
Te6edf3last_viewed_at Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_viewed_atTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3last_viewed_atTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_viewed_at Tff7b72= Te6edf3last_viewed_atTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4)
Tff7b72return Te6edf3message_timestamp Tff7b72<Tff7b72= Te6edf3last_viewed_atTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)
T8b949e# Folders
Tff7b72def Td2a8ffget_all_foldersTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_folders ORDER BY name ASCTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffcreate_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffINSERT INTO lxmf_folders (name, created_at, updated_at) VALUES (?, ?, ?)Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3nameTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffrename_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3folder_idTb4b4b4, Te6edf3new_nameTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_folders SET name = ?, updated_at = ? WHERE id = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3new_nameTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffdelete_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_folders WHERE id = ?Ta5d6ff"Tb4b4b4, Tb4b4b4(Te6edf3folder_idTb4b4b4,Tb4b4b4)Tb4b4b4)
Tff7b72def Td2a8ffget_conversation_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_conversation_folders WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffmove_conversation_to_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3folder_id Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_folders WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_folders (peer_hash, folder_id, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(peer_hash) DO UPDATE SET
folder_id = EXCLUDED.folder_id,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4, Te6edf3folder_idTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffmove_conversations_to_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashesTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3peer_hashesTb4b4b4:
Tff7b72return
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72if Te6edf3folder_id Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3peer_hashesTb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_folders WHERE peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3peer_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecutemanyTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_folders (peer_hash, folder_id, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(peer_hash) DO UPDATE SET
folder_id = EXCLUDED.folder_id,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4[Tb4b4b4(Te6edf3hTb4b4b4, Te6edf3folder_idTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3peer_hashesTb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffget_all_conversation_foldersTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_conversation_foldersTa5d6ff"Tb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────